package com.javaking.clanteam.studentutils.utils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.Spinner;
import android.widget.TextView;

import com.javaking.clanteam.studentutils.R;

public class EditableListFragment<T> extends ListFragment implements OnItemsChangedListener<T>{
	
	public static final String CAN_ADD_ITEMS = "canAdd";

	private List<Map<String,T>> mAdapterData;
	private List<T> mData;
	
	private BaseAdapter mAdapter;
	
	private Context mContext;
	private OnItemsChangedListener<T> mListener;
	private Fragment mAdderFrag;
	
	/**
	 * Construct a new {@link EditableListFragment} and add it to the frame in 
	 * @param parent The fragment that owns the fragment to be created.
	 * 
	 * @param startingData Any and all data that this list should start with. <br>
	 * 		(<b>NOTE:</b> When displaying the items on-screen, <code>toString()</code>
	 * 		function of <tt>T</tt> is used to determine what should be shown.)
	 * 
	 * @param canAdd A boolean telling the list whether or not it should be able 
	 * 	to add items to the list.
	 * 
	 * @param addItemFragment The {@link Fragment} to be shown when the user clicks
	 * 	the add button. May be null iff <code>canAdd</code> is set to false.
	 * 		(<b>NOTE:</b> Although and fragment can be used, expect best results
	 * 		if you provide a {@link DialogFragment}.)
	 * 
	 * @param strict If true, forces the all parameters to match implementations
	 * otherwise it won't care.
	 *  
	 * @return The {@link EditableListFragment} that was constructed.
	 * 
	 * @throws IllegalArgumentException If <code>parent</code> does not implement
	 *  {@link OnItemsChangedListener}
	 *  
	 * @throws IllegalStateException If the currently visible screen does not have
	 *  a {@link FrameLayout} with the proper ID to inject the new list into.
	 */
	public static <T> EditableListFragment<T> addToFrame(FragmentActivity parent,
			List<T> startingData, boolean canAdd, Class<? extends Fragment> addItemFragment, boolean strict) {
		
		
		View view = parent.findViewById(R.id.frame);
		if (view==null||view.getVisibility()!=View.VISIBLE||!(view instanceof FrameLayout)) {
			throw new IllegalStateException("The visible screen does not contain" +
					" a frame to inject into.");
		}
		
		EditableListFragment<T> frag = createInstance(parent, startingData, canAdd, addItemFragment, strict);
		
		
		parent.getSupportFragmentManager().beginTransaction()
			.replace(R.id.frame, frag,frag.getClass().getName()).commit();
		return frag;
	}
	
	/**
	 * @see EditableListFragment#addToFrame(Fragment, List, boolean, Class, boolean)
	 */
	public static <T> EditableListFragment<T> addToFrame(FragmentActivity parent,
			List<T> startingData, boolean canAdd, Class<? extends Fragment> addItemFragment) {
		return addToFrame(parent, startingData, canAdd, addItemFragment, true);
	}
	
	
	/**
	 * Construct a new {@link EditableListFragment}
	 * @param parent The fragment that owns the fragment to be created.
	 * 
	 * @param startingData Any and all data that this list should start with. <br>
	 * 		(<b>NOTE:</b> When displaying the items on-screen, <code>toString()</code>
	 * 		function of <tt>T</tt> is used to determine what should be shown.)
	 * 
	 * @param canAdd A boolean telling the list whether or not it should be able 
	 * 	to add items to the list.
	 * 
	 * @param addItemFragment The {@link Fragment} to be shown when the user clicks
	 * 	the add button. May be null iff  <code>canAdd</code> is set to false.
	 * 		(<b>NOTE:</b> Although and fragment can be used, expect best results
	 * 		if you provide a {@link DialogFragment}.)
	 * 
	 * @param strict If true, forces the all parameters to match implementations
	 * otherwise it won't care.
	 *  
	 * @return The {@link EditableListFragment} that was constructed.
	 * 
	 * @throws IllegalArgumentException If <code>parent</code> does not implement
	 *  {@link OnItemsChangedListener}
	 */
	@SuppressWarnings("unchecked")
	public static <T> EditableListFragment<T> createInstance(FragmentActivity parent,
			List<T> startingData, boolean canAdd, Class<? extends Fragment> addItemFragment, boolean strict) {
		
		
		if (strict && !(parent instanceof OnItemsChangedListener)) {
			throw new IllegalArgumentException("Parent fragment must implement" +
					" OnItemsChangedListener");
		}
		if ((canAdd)&&(!Arrays.asList(addItemFragment.getInterfaces()).contains(ListAdder.class))) {
			throw new IllegalArgumentException(addItemFragment.getName()+" must implement " +
					"ListAdder so that the list can retrieve the added item.");
		}
		
		final EditableListFragment<T> frag = new EditableListFragment<T>();
		
		frag.mContext = parent;
		frag.mAdapterData = new ArrayList<Map<String,T>>();
		frag.mAdapter = new ListAdapter(frag.mContext, frag.mAdapterData); 
		frag.mData = new ArrayList<T>();
		if (strict) {
			frag.mListener = (OnItemsChangedListener<T>) parent;
		}
		
		Bundle adderArgs = new Bundle();
		adderArgs.putString("parentFrag", frag.getClass().getName());
		if (canAdd) {
			frag.mAdderFrag = Fragment.instantiate(parent,addItemFragment.getName(), adderArgs);
			((ListAdder<T>)frag.mAdderFrag).setListener(frag);
		}
		if (startingData!=null) {
			for (T u : startingData) {
				frag.addItem(u);
			}
		}
		Bundle args = new Bundle();
		args.putBoolean(CAN_ADD_ITEMS, canAdd);
		frag.setArguments(args);
		
		return frag;
	}
	
	/**
	 * @see EditableListFragment#createInstance(Fragment, List, boolean, Class, boolean) 
	 */
	public static <T> EditableListFragment<T> createInstance(FragmentActivity parent,
			List<T> startingData, boolean canAdd, Class<? extends Fragment> addItemFragment) {
		return createInstance(parent, startingData, canAdd, addItemFragment, true);
	}
	
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		
		View view = inflater.inflate(R.layout.fragment_editablelist, container,false);
		((TextView)view.findViewById(android.R.id.empty)).setText("There are no items in this list.");
		
		View addButton = view.findViewById(R.id.addTimeButton);
		View removeButton = view.findViewById(R.id.removeTimeButton);
		if(!getArguments().getBoolean(CAN_ADD_ITEMS)) {
			addButton.setVisibility(View.GONE);
			removeButton.setVisibility(View.GONE);
		} else {
			addButton.setOnClickListener(new View.OnClickListener() {
				
				@Override
				public void onClick(View v) {
					Fragment adder;
					adder = mAdderFrag;
					if (adder instanceof DialogFragment) {
						((DialogFragment)adder).show(getFragmentManager(), getClass().getName());
					} else  {
						getFragmentManager().beginTransaction()
							.replace(android.R.id.content, adder)
							.addToBackStack(getClass().getName())
							.commit();
					}
				}
			});
			removeButton.setOnClickListener(new View.OnClickListener() {
				
				@Override
				public void onClick(View v) {
					final Spinner spinner = new Spinner(mContext);
					ArrayAdapter<String> adapter = new ArrayAdapter<String>(mContext,
							android.R.layout.simple_spinner_dropdown_item);
					for (Iterator<T> i = mData.iterator(); i.hasNext();) {
						adapter.add(i.next().toString());
					}
					spinner.setAdapter(adapter);
					AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
					builder.setTitle("Which item do you want to remove?");
					builder.setView(spinner);
					builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
						
						@Override
						public void onClick(DialogInterface dialog, int which) {
							dialog.cancel();
						}
					});
					builder.setPositiveButton(R.string.menu_delete, new DialogInterface.OnClickListener() {
						
						@Override
						public void onClick(DialogInterface dialog, int which) {
							int index = spinner.getSelectedItemPosition();
							onItemRemoved(mData.get(index));
							dialog.dismiss();
						}
					});
					
					builder.show();
				}
			});
		}
		
		setListAdapter(mAdapter);
		
		return view;
	}
	
	/**
	 * Updates the list of items. This merely drops all items then adds the ones
	 * from the provided {@link List}. This is NOT an efficient way to add an item
	 * manually, but provides an easy way to update the list.
	 * @param items
	 */
	public void updateData(List<T> items) {
		mData.clear();
		mData.addAll(items);
		mAdapter.notifyDataSetChanged();
	}
	
	
	@Override
	public void onItemsAdded(List<T> items) {
		for (T item : items) {
			addItem(item);
		}
		
		if (mListener!=null) {
			mListener.onItemsAdded(items);
		}
	}

	public void addItem(T item) {
		if (!mData.contains(item)) {
			mData.add(item);
			Map<String,T> itemMap = new HashMap<String, T>();
			itemMap.put("item", item);
			
			mAdapterData.add(itemMap);
			
			mAdapter.notifyDataSetChanged();
		}
	}
	
	public List<T> getItems() {
		return mData;
	}

	@Override
	public void onItemRemoved(T item) {
		mData.remove(item);
		mAdapter.notifyDataSetChanged();
		
		if (mListener!=null) {
			mListener.onItemRemoved(item);
		}
	}
}
